package org.example.verify;

import org.example.csv.CSVData;
import org.example.util.CSVUtils;
import org.example.util.FileUtils;
import org.junit.Test;

import java.nio.file.Path;
import java.text.DecimalFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class Case4Verify {

    public static final String basicPath = "D:\\Development\\GeaFlowCompetition\\sf1\\snapshot";

    public static final String outputPath = "D:\\Development\\GeaFlowCompetition\\output";

    public static final String outputName = "result4-sf1-check.csv";

    Map<String, Double> resMap = new HashMap<>();

    Map<String, Set<String>> checkCalc = new HashMap<>();

    List<String> path = new ArrayList<>();

    Map<String, List<List<String>>> pathMap = new HashMap<>();

    Map<String, Set<String>> personOuts = new HashMap<>();

    @Test
    public void start() {
        Set<String> allPersonSet = readPersonEntity();
        Map<String, Double> loanMap = readLoanEntity();

        Path personApplyLoan = FileUtils.pathJoin(basicPath, "PersonApplyLoan.csv");
        CSVData personApplyLoanData = CSVUtils.read(personApplyLoan);
        List<String> persons = personApplyLoanData.getByColumn("personId");
        List<String> loans = personApplyLoanData.getByColumn("loanId");
        // 92078
        System.out.println("person apply loan edge size: " + persons.size());

        // 检查 person ---apply---> loan 不可能重复
        // 每个人申请的贷款是唯一的，与 id 一一对应
        Map<String, Set<String>> checkMap = new HashMap<>();
        Set<String> loanCheckSet = new HashSet<>();
        for (int i = 0; i < persons.size(); i++) {
            String person = persons.get(i);
            String loan = loans.get(i);
            Set<String> set;
            if (checkMap.containsKey(person)) {
                set = checkMap.get(person);
                if (set.contains(loan)) {
                    System.out.println("person + loan has same");
                }
            } else {
                set = new HashSet<>();
            }
            set.add(loan);
            checkMap.put(person, set);
            if (loanCheckSet.contains(loan)) {
                System.out.println(loan + " already exists.");
            } else {
                loanCheckSet.add(loan);
            }

            if (!loanMap.containsKey(loan)) {
                System.out.println("loan not exists");
            }
        }
        System.out.println("apply loan set size: " + loanCheckSet.size());

        Map<String, Double> personApplyLoanMap = new HashMap<>();
        Map<String, List<String>> personApplyLoanIdMap = new HashMap<>();
        for (int i = 0; i < persons.size(); i++) {
            String person = persons.get(i);
            String loanId = loans.get(i);
            // personApplyLoanMap.put(person, personApplyLoanMap.getOrDefault(person, 0.0) + loanMap.get(loanId));
            if (!loanMap.containsKey(loanId)) {
                System.out.println("load id not exists");
            }
            if (personApplyLoanMap.containsKey(person)) {
                personApplyLoanMap.put(person, personApplyLoanMap.get(person) + loanMap.get(loanId));
                List<String> personApplyLoans = personApplyLoanIdMap.get(person);
                personApplyLoans.add(loanId);
                personApplyLoanIdMap.put(person, personApplyLoans);
            } else {
                personApplyLoanMap.put(person, loanMap.get(loanId));
                List<String> list = new ArrayList<>();
                list.add(loanId);
                personApplyLoanIdMap.put(person, list);
            }
        }

        // every person load money number
        // for (String person : personApplyLoanMap.keySet()) {
        //     System.out.println("person = " + person + ", apply number = " + personApplyLoanMap.get(person));
        // }

        Path personGuaranteePerson = FileUtils.pathJoin(basicPath, "PersonGuaranteePerson.csv");
        CSVData personGuaranteePersonData = CSVUtils.read(personGuaranteePerson);
        List<String> fromList = personGuaranteePersonData.getByColumn("fromId");
        List<String> toList = personGuaranteePersonData.getByColumn("toId");
        System.out.println("person guarantee person edge size: " + fromList.size());
        Set<String> check = new HashSet<>();
        Map<String, List<String>> inMap = new HashMap<>();
        Map<String, List<String>> outMap = new HashMap<>();
        Set<String> personIdSet = new HashSet<>();
        int n = fromList.size();
        for (int i = 0; i < n; i++) {
            String from = fromList.get(i);
            String to = toList.get(i);
            if (!allPersonSet.contains(from)) {
                System.out.println("from not contains");
            }
            if (!allPersonSet.contains(to)) {
                System.out.println("to not contains");
            }

            if (loanMap.containsKey(from) || loanMap.containsKey(to)) {
                System.out.println("person id and loan id exist same");
            }
            personIdSet.add(from);
            personIdSet.add(to);
            String concatId = from + "#" + to;
            if (check.contains(concatId)) {
                System.out.println("fromId -> toId has same");
            } else {
                check.add(concatId);
            }

            List<String> outList = outMap.getOrDefault(from, new ArrayList<>());
            outList.add(to);
            outMap.put(from, outList);

            List<String> inList = inMap.getOrDefault(to, new ArrayList<>());
            inList.add(from);
            inMap.put(to, inList);
        }

        System.out.println("personIdSet: " + personIdSet.size());
        System.out.println("out size: " + outMap.size());
        System.out.println("in size: " + inMap.size());

        // multi out edges
        // for (String out : outMap.keySet()) {
        //     List<String> lst = outMap.get(out);
        //     if (lst.size() > 1) {
        //         System.out.println(out + lst);
        //     }
        // }

        // multi in edges
        // for (String in : inMap.keySet()) {
        //     List<String> lst = inMap.get(in);
        //     if (lst.size() > 1) {
        //         System.out.println(in + lst);
        //     }
        // }

        Map<String, List<String>> personMap = new HashMap<>();
        for (int i = 0; i < fromList.size(); i++) {
            String from = fromList.get(i), to = toList.get(i);
            // if (personMap.containsKey(from)) {
            //     System.out.println("guarantee exist twice");
            // }
            List<String> list = personMap.getOrDefault(from, new ArrayList<>());
            list.add(to);
            personMap.put(from, list);
        }

        System.out.println("person size: " + personMap.size());

        for (String key : personMap.keySet()) {
            List<String> v1 = personMap.get(key);
            List<String> v2 = outMap.get(key);
            if (!v1.equals(v2)) {
                System.out.println("out not equal");
            }
        }

        // for (String person : personMap.keySet()) {
        //     dfsOpt(person, person, personMap, personApplyLoanMap, 0);
        // }

        for (String person : personMap.keySet()) {
            dfsPath(person, person, personMap, 0);
//            bfsPath(person, personMap);
        }

        for (String person : pathMap.keySet()) {
            List<List<String>> lst = pathMap.get(person);
//            System.out.println(person + " ==> " + lst);
//            System.out.println(lst);

            for (List<String> item : lst) {
                int nn = item.size();
                Set<String> tmpSet = new HashSet<>(item);
                tmpSet.add(person);
                if (tmpSet.size() != nn + 1) {
                    System.out.println("==========================");
                    System.out.println("has circles");
                    System.out.println(person);
                    System.out.println(item);
                    System.out.println("==========================");
                }
//                if (item.size() == 3) {
//                    Set<String> tmpSet = new HashSet<>(item);
//                    tmpSet.add(person);
//                    if (tmpSet.size() == 2) {
//                        System.out.println("==========================");
//                        System.out.println("two circles");
//                        System.out.println(person);
//                        System.out.println(item);
//                        System.out.println("==========================");
//                    }
//                }
            }
        }

//        for (String person : personOuts.keySet()) {
//            Set<String> strings = personOuts.get(person);
//            System.out.println(person);
//            System.out.println(strings);
//        }

        // for (String key : pathMap.keySet()) {
        //     List<List<String>> lst = pathMap.get(key);
        //     Set<String> set = new HashSet<>();
        //     for (List<String> strings : lst) {
        //         set.addAll(strings);
        //     }
        //     double sum = 0.0;
        //     for (String item : set) {
        //         sum += personApplyLoanMap.getOrDefault(item, 0.0);
        //     }
        //     resMap.put(key, sum);
        // }

        // ========================dfs==========================
        for (String key : pathMap.keySet()) {
            List<List<String>> lst = pathMap.get(key);
            Set<String> set = new HashSet<>();
            for (List<String> strings : lst) {
                Set<String> tmp = new HashSet<>();
                tmp.add(key);
                tmp.addAll(strings);
                set.addAll(strings);
                if (tmp.size() != strings.size() + 1) {
                    System.out.println("has cycle");
                }
            }
            // List<String> set = new ArrayList<>();
            // for (List<String> strings : lst) {
            //     for (String string : strings) {
            //         set.add(string);
            //     }
            // }
            double sum = 0.0;
            Set<String> loanSet = new HashSet<>();
            for (String item : set) {
                // sum += personApplyLoanMap.getOrDefault(item, 0.0);
                List<String> list = personApplyLoanIdMap.getOrDefault(item, new ArrayList<>());
                loanSet.addAll(list);
            }

            for (String item : loanSet) {
                sum += loanMap.get(item);
            }
            resMap.put(key, sum);
        }

        // ======================bfs==========================
//        for (String key : personOuts.keySet()) {
//            Set<String> outs = personOuts.get(key);
//
//            if (outs.contains(key)) {
//                System.out.println("out contains");
//            }
//
//            Set<String> ids = new HashSet<>();
//            for (String out : outs) {
//                if (personApplyLoanIdMap.containsKey(out)) {
//                    ids.addAll(personApplyLoanIdMap.get(out));
//                }
//            }
//
//            double sum = 0.0;
//            for (String id : ids) {
//                sum += loanMap.get(id);
//            }
//            resMap.put(key, sum);
//        }

        writeToFile();
    }

    private Set<String> readPersonEntity() {
        Path personPath = FileUtils.pathJoin(basicPath, "Person.csv");
        CSVData personData = CSVUtils.read(personPath);
        Set<String> allPersonSet = new HashSet<>(personData.getByColumn("personId"));
        // 77671 unique
        System.out.println("person entity size: " + allPersonSet.size());
        return allPersonSet;
    }

    private Map<String, Double> readLoanEntity() {
        Path loadPath = FileUtils.pathJoin(basicPath, "Loan.csv");
        CSVData loanData = CSVUtils.read(loadPath);
        // loanId -> loanAmount (loan id is unique)
        Map<String, Double> loanMap = new HashMap<>();
        List<String> loanList = loanData.getByColumn("loanId");
        List<String> loanAmountList = loanData.getByColumn("loanAmount");

        for (int i = 0; i < loanList.size(); i++) {
            String loanId = loanList.get(i);
            if (loanMap.containsKey(loanId)) {
                System.out.println("load id has existed");
            } else {
                double loanValue = Double.parseDouble(loanAmountList.get(i));
                if (loanValue == 0.0) {
                    System.out.println("loan == 0.0");
                }
                loanMap.put(loanId, loanValue);
            }
        }
        // 137811 unique
        System.out.println("loan entity size: " + loanMap.size());
        return loanMap;
    }

    private void writeToFile() {
        DecimalFormat df = new DecimalFormat("#.00");
        CSVData writer = new CSVData(Arrays.asList("id", "value"));
        List<Long> ids = new ArrayList<>();
        for (String key : resMap.keySet()) {
            if (resMap.get(key) != 0.0) {
                ids.add(Long.valueOf(key));
            }
        }
        Collections.sort(ids);
        for (Long id : ids) {
            String key = String.valueOf(id);
            double value = resMap.get(key);
            // if (value == 0.0) {
            //     writer.addLine(new String[]{key, "0.00"});
            // } else {
            //     writer.addLine(new String[]{key, df.format(value)});
            // }

            if (value == 0.0) continue;

//            double res = (Math.round(value / 100_000_000 * 100) / 100.0);
            String sv = String.format("%.2f", value / 100_000_000);
            writer.addLine(new String[]{key, sv});
        }
        CSVUtils.write(FileUtils.pathJoin(outputPath, outputName), writer);
    }

    private void bfsPath(final String person,
                         final Map<String, List<String>> personMap) {

        int u = 0;
        Deque<String> deque = new ArrayDeque<>();
        deque.offer(person);

        while (!deque.isEmpty() && u < 3) {
            int sz = deque.size();
            for (int i = 0; i < sz; i++) {
                String poll = deque.poll();
                if (personMap.containsKey(poll)) {
                    List<String> outs = personMap.get(poll);
                    Set<String> set = personOuts.getOrDefault(person, new HashSet<>());
                    set.addAll(outs);
                    personOuts.put(person, set);
                    for (String out : outs) {
                        deque.offer(out);
                    }
                }
            }
            u++;
        }
    }

    private void dfsPath(final String person,
                         String currentPerson,
                         final Map<String, List<String>> personMap,
                         int u
    ) {

        if (u > 0 && Objects.equals(person, currentPerson)) {
            System.out.println("two person equals");
        }

//        if (path.contains(currentPerson)) {
//            System.out.println("has cycle");
//        }

        if (u >= 3) {
            // if (!personMap.containsKey(currentPerson) || personMap.get(currentPerson).isEmpty()) {
            //     List<List<String>> pathList = pathMap.getOrDefault(person, new ArrayList<>());
            //     pathList.add(new ArrayList<>(path));
            //     pathMap.put(person, pathList);
            // }
            List<List<String>> pathList = pathMap.getOrDefault(person, new ArrayList<>());
            pathList.add(new ArrayList<>(path));
            pathMap.put(person, pathList);
            return;
        }

        List<String> outs = personMap.getOrDefault(currentPerson, new ArrayList<>());
        if (!outs.isEmpty()) {
            for (String out : outs) {
                path.add(out);
                dfsPath(person, out, personMap, u + 1);
                path.remove(path.size() - 1);
            }
        } else {
            List<List<String>> pathList = pathMap.getOrDefault(person, new ArrayList<>());
            pathList.add(new ArrayList<>(path));
            pathMap.put(person, pathList);
        }
    }

    private void dfsOpt(final String person,
                        String currentPerson,
                        final Map<String, List<String>> personMap,
                        final Map<String, Double> personApplyLoanMap,
                        int u
    ) {
        if (u >= 3) {
            if (!personMap.containsKey(currentPerson) || personMap.get(currentPerson).isEmpty()) {
                resMap.put(person, resMap.getOrDefault(person, 0.0));
            }
            return;
        }

        List<String> outs = personMap.getOrDefault(currentPerson, new ArrayList<>());
        if (Objects.equals(person, "28587302382037")) {
            System.out.println(outs);
        }
        for (String out : outs) {
            // if (!personApplyLoanMap.containsKey(out)) {
            //     continue;
            // }
            Set<String> set = checkCalc.getOrDefault(person, new HashSet<>());
            set.add(person);
            if (!set.contains(out)) {
                set.add(out);
                checkCalc.put(person, set);
                double row = personApplyLoanMap.getOrDefault(out, 0.0);
                if (!personMap.containsKey(out) || personMap.get(out).isEmpty()) {
                    if (Objects.equals(person, "28587302382037")) {
                        System.out.println("--------------------------------------------------");
                        System.out.println(u);
                        System.out.println(currentPerson);
                        System.out.println(out);
                        System.out.println(resMap.get(person));
                        System.out.println(row);
                        System.out.println("==================================================");
                    }
                    resMap.put(person, resMap.getOrDefault(person, 0.0) + row);
                } else {
                    resMap.put(person, resMap.getOrDefault(person, 0.0) + row);
                    dfsOpt(person, out, personMap, personApplyLoanMap, u + 1);
                }
            } else {
                System.out.println(person + "->" + currentPerson + "=>" + out + " already exists");
            }

            // double row = personApplyLoanMap.getOrDefault(out, 0.0);
            // resMap.put(person, resMap.getOrDefault(person, 0.0) + row);
            // dfs(person, out, personMap, personApplyLoanMap,  u + 1);
        }
    }

    private void dfs(final String person,
                     String currentPerson,
                     final Map<String, List<String>> personMap,
                     final Map<String, Double> personApplyLoanMap,
                     int u
    ) {

        if (Objects.equals(person, "28587302382037")) {
            System.out.println(resMap.get(person));
        }

        if (u >= 3) {
            return;
        }

        List<String> outs = personMap.getOrDefault(currentPerson, new ArrayList<>());
        for (String out : outs) {
            // if (!personApplyLoanMap.containsKey(out)) {
            //     continue;
            // }
            Set<String> set = checkCalc.getOrDefault(person, new HashSet<>());
            set.add(person);
            if (!set.contains(out)) {
                double row = personApplyLoanMap.getOrDefault(out, 0.0);
                if (Objects.equals(person, "28587302382037")) {
                    System.out.println("--------------------------------------------------");
                    System.out.println(out);
                    System.out.println(resMap.get(person));
                    System.out.println(row);
                    System.out.println("==================================================");
                }
                resMap.put(person, resMap.getOrDefault(person, 0.0) + row);
                set.add(out);
                checkCalc.put(person, set);
                dfs(person, out, personMap, personApplyLoanMap, u + 1);
            } else {
                System.out.println(person + "->" + currentPerson + "=>" + out + " already exists");
            }

            // double row = personApplyLoanMap.getOrDefault(out, 0.0);
            // resMap.put(person, resMap.getOrDefault(person, 0.0) + row);
            // dfs(person, out, personMap, personApplyLoanMap,  u + 1);
        }
    }
}
